package org.xx.armory.commons;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import static org.apache.commons.lang3.StringUtils.trimToNull;
import static org.apache.commons.lang3.math.NumberUtils.toByte;
import static org.apache.commons.lang3.math.NumberUtils.toDouble;
import static org.apache.commons.lang3.math.NumberUtils.toInt;
import static org.apache.commons.lang3.math.NumberUtils.toLong;
import static org.apache.commons.lang3.math.NumberUtils.toShort;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 表示名字和对象映射的包。
 *
 * @author Haart
 */
public class NameObjectBag
        implements Iterable<NameObject> {
    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss Z";
    private final Map<String, Object> data;

    /**
     * 构造名字和对象映射的包。
     */
    public NameObjectBag() {
        this.data = new HashMap<>();
    }

    public NameObjectBag(
            NameObjectBag other
    ) {
        if (other != null) {
            this.data = new HashMap<>(other.data);
        } else {
            this.data = new HashMap<>();
        }
    }

    /**
     * 获取包的长度。
     *
     * @return 包的长度。
     */
    public final int size() {
        return this.data.size();
    }

    /**
     * 判断包是否为空。
     *
     * @return 如果包为空则返回{@code true}，否则返回{@code false}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final boolean isEmpty() {
        return this.data.isEmpty();
    }

    /**
     * 判断包是否包含指定的名字。
     *
     * @param name
     *         指定的名字。
     * @return 如果包含则返回{@code true}，否则返回{@code false}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final boolean containsName(
            String name
    ) {
        name = notBlank(name, "name").trim();

        return this.data.containsKey(name);
    }

    /**
     * 向包内添加一个数据。
     *
     * @param item
     *         待添加的数据。
     * @throws IllegalArgumentException
     *         如果参数{@code item}是{@code null}。
     */
    public final void add(
            NameObject item
    ) {
        notNull(item, "item");

        this.data.put(item.getName(), item.getObject());
    }

    /**
     * 向包内添加一批数据。
     *
     * @param bag
     *         待添加的数据。
     * @throws IllegalArgumentException
     *         如果参数{@code m}是{@code null}，或者参数{@code m}中存在{@code null}名字。
     */
    public final void addAll(
            NameObjectBag bag
    ) {
        notNull(bag, "bag");

        bag.forEach(this::add);
    }

    /**
     * 向包内添加一批数据。
     *
     * @param m
     *         待添加的数据。
     * @throws IllegalArgumentException
     *         如果参数{@code m}是{@code null}，或者参数{@code m}中存在{@code null}名字。
     */
    public final void addAll(
            Map<String, Object> m
    ) {
        notNull(m, "m");

        m.forEach(this::putObject);
    }

    /**
     * 清空包的内容，清空以后包的长度为0。
     */
    public final void clear() {
        this.data.clear();
    }

    /**
     * 删除指定名字。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @return 原来的值，如果指定的名字不存在则返回{@code null}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final Object remove(
            String name
    ) {
        name = notBlank(name, "name").trim();

        return this.data.remove(name);
    }

    /**
     * 根据名字获取值。
     *
     * @param name
     *         名字。
     * @return 符合该名字的值。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final Object getObject(
            String name
    ) {
        name = notBlank(name, "name").trim();

        return this.data.get(name);
    }

    /**
     * 添加指定的名字和值。
     *
     * @param name
     *         名字。
     * @param value
     *         值，如果此参数是{@code null}则删除该名字。
     * @return 此名字之前关联的值。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final Object putObject(
            String name,
            Object value
    ) {
        name = notBlank(name, "name").trim();

        if (value != null) {
            return this.data.put(name, value);
        } else {
            return this.data.remove(name);
        }
    }

    /**
     * 获取指定名字的字符串值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @return 值，如果指定的名字不存在则返回空字符串。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final String getString(
            String name
    ) {
        return getString(name, "");
    }

    /**
     * 设置或者删除指定名字的字符串值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param value
     *         值，如果此参数的值是{@code null}或者是空字符串则删除该名字。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public void putString(
            String name,
            String value
    ) {
        value = trimToNull(value);

        putObject(name, value);
    }

    /**
     * 获取指定名字的字符串值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在则返回默认值。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final String getString(
            String name,
            String defaultValue
    ) {
        final Object result = getObject(name);
        return result != null ? result.toString() : defaultValue;
    }

    /**
     * 获取指定名字的字节值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为字节则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final byte getByte(
            String name,
            byte defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).byteValue();
        } else if (value instanceof CharSequence) {
            return toByte(value.toString());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的短整数值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为短整数则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final short getShort(
            String name,
            short defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).shortValue();
        } else if (value instanceof CharSequence) {
            return toShort(value.toString());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的整数值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为整数则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final int getInteger(
            String name,
            int defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof CharSequence) {
            return toInt(value.toString());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的长整数值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为长整数则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final long getLong(
            String name,
            long defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).longValue();
        } else if (value instanceof CharSequence) {
            return toLong(value.toString());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的双精度值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为双精度则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final double getDouble(
            String name,
            double defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Number) {
            return ((Number) value).doubleValue();
        } else if (value instanceof CharSequence) {
            return toDouble(value.toString());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的boolean值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为boolean则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final boolean getBoolean(
            String name,
            boolean defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof CharSequence) {
            final String s = value.toString();
            return "yes".equals(s) || "true".equals(s) || "on".equals(s) || "1".equals(s) || "y".equals(s) || "t".equals(s);
        } else if (value instanceof Number) {
            return ((Number) value).byteValue() != 0;
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的十进制值。
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为十进制则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final BigDecimal getDecimal(
            String name,
            BigDecimal defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        } else if (value instanceof CharSequence) {
            try {
                return new BigDecimal(value.toString());
            } catch (NumberFormatException ex) {
                return defaultValue;
            }
        } else if (value instanceof Number) {
            return new BigDecimal(((Number) value).doubleValue());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的日期值。
     *
     * @param name
     *         名字
     * @param defaultValue
     *         默认值。
     * @return 包中指定名字对应的{@code java.util.Date}值，如果不存在则返回{@code defaultValue}。
     * @see #getLong(String, long)
     * @see Date#Date(long)
     */
    public final Date getDate(
            String name,
            Date defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof Date) {
            return (Date) value;
        } else if (value instanceof CharSequence) {
            try {
                return new SimpleDateFormat(DATE_FORMAT).parse(value.toString());
            } catch (ParseException e) {
                return defaultValue;
            }
        } else if (value instanceof Number) {
            return new Date(((Number) value).longValue());
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的字节数组值。
     *
     * <p>该名字对应的值应当是空字符串或者{@literal base64}编码的字符串。</p>
     *
     * @param name
     *         名字，自动去掉首尾空格。
     * @param defaultValue
     *         默认值。
     * @return 值，如果指定的名字不存在或者无法被转换为字节数组则返回{@code defaultValue}。
     * @throws IllegalArgumentException
     *         如果参数{@code name}是{@code null}或者只包含空白字符。
     */
    public final byte[] getBytes(
            String name,
            byte[] defaultValue
    ) {
        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (value instanceof CharSequence) {
            try {
                return Base64.getDecoder().decode(value.toString());
            } catch (IllegalArgumentException ignored) {
                return defaultValue;
            }
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取指定名字的{@link Enum}对象。
     *
     * @param <T>
     *         {@link Enum}对象的类型。
     * @param clazz
     *         {@link Enum}对象的类型。
     * @param name
     *         名字
     * @param defaultValue
     *         缺省值
     * @return 指定名字对应的 {@link Enum}对象，如果不存在则返回{@code defaultValue}。
     */
    public final <T extends Enum<T>> T getEnum(
            Class<T> clazz,
            String name,
            T defaultValue
    ) {
        notNull(clazz, "clazz");

        final Object value = getObject(name);
        if (value == null) {
            return defaultValue;
        } else if (clazz.isAssignableFrom(value.getClass())) {
            return clazz.cast(value);
        } else if (value instanceof CharSequence) {
            final String s = value.toString();
            for (final T c : clazz.getEnumConstants()) {
                if (c.name().equals(s)) {
                    return c;
                }
            }
            return defaultValue;
        } else {
            return defaultValue;
        }
    }

    /**
     * 向包中加入一个整数值。
     *
     * @param name
     *         名字。
     * @param value
     *         值。
     */
    public void putInteger(
            String name,
            int value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个长整数值。
     *
     * @param name
     *         名字。
     * @param value
     *         对应的值。
     */
    public void putLong(
            String name,
            long value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个双精度数值。
     *
     * @param name
     *         名字。
     * @param value
     *         对应的值。
     */
    public void putDouble(
            String name,
            double value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个布尔值。
     *
     * @param name
     *         名字。
     * @param value
     *         布尔值。
     */
    public void putBoolean(
            String name,
            boolean value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个{@link Enum}对象。
     *
     * @param name
     *         名字。
     * @param value
     *         对应的值。
     */
    public void putEnum(
            String name,
            Enum<?> value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个日期值。
     *
     * @param name
     *         名字。
     * @param value
     *         对应的值。
     */
    public void putDate(
            String name,
            Date value
    ) {
        putObject(name, value);
    }

    /**
     * 向包中加入一个十进制值。
     *
     * @param name
     *         名字。
     * @param d
     *         对应的值。
     */
    public void putDecimal(
            String name,
            BigDecimal d
    ) {
        putObject(name, d);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterator<NameObject> iterator() {
        final Iterator<Map.Entry<String, Object>> entryIterator = this.data.entrySet().iterator();
        return new Iterator<NameObject>() {
            @Override
            public boolean hasNext() {
                return entryIterator.hasNext();
            }

            @Override
            public NameObject next() {
                final Map.Entry<String, Object> entry = entryIterator.next();
                return new NameObject(entry.getKey(), entry.getValue());
            }
        };
    }
}
